#include "scene.hh"
#include "render.hh"
#include "../log.hh"
#include "../perlin.hh"
#include "gl.hh"
#include <algorithm>
#include <cmath>
//#include <glm/gtc/noise.hpp>

static bool in_range(float x, float f, float t)
{
	return (f < x && x < t);
}

static float unlirp(float x, float f, float t)
{
	return (x-f) / (t-f);
}

static void color_irp(float result[3], float x)
{
	static float table[][3] = {
		{ 1.0, 0.0, 0.0 },
		{ 1.0, 0.6, 0.0 },
		{ 1.0, 1.0, 0.0 },
		{ 0.0, 1.0, 0.0 },
		{ 0.3, 0.3, 1.0 },
		{ 0.0, 1.0, 1.0 },
		{ 1.0, 0.0, 1.0 },
	};
	constexpr auto ncolor = sizeof(table)/sizeof(table[0]);
	x *= ncolor;
	int ci = int(x) % ncolor;
	int ni = (ci+1) % ncolor;
	float a = x - ci;
	float ca = 1.0 - a;
	auto c = table[ci];
	auto n = table[ni];
	result[0] = ca*c[0] + a*n[0];
	result[1] = ca*c[1] + a*n[1];
	result[2] = ca*c[2] + a*n[2];
}

namespace gl
{
	/////////////////////////////////////////////////
	//
	// scene::cube
	//

	void scene::cube::render() const
	{
		gl::push_matrix();
		gl::translatef(pos.x, pos.y, pos.z);
		gl::scalef(1, 1, 0.5);
		gl::color3fv(&fcolor[0]);
		render::cube(true);
		gl::color3fv(&bcolor[0]);
		render::cube(false);
		gl::pop_matrix();
	}

	static bool operator < (glm::vec3 const& a, glm::vec3 const& b)
	{
		return (a[0]<b[0] && a[1]<b[1] && a[2]<b[2]);
	}

	bool scene::cube::contain(glm::vec3 const& point) const
	{
		// cubes are all the same size in a
		// [-dim, +dim] axis-aligned region
		glm::vec3 dim{0.5, 0.5, 0.5};	// dimension
		auto min = pos - dim;
		auto max = pos + dim;
		return (min < point && point < max);
	}


	/////////////////////////////////////////////////
	//
	// scene
	//

	void scene::render(context& ctx) const
	{
		ctx.fog(2, z_range-1);
		gl::line_width(2);
		for (auto& cube: cubes) cube();
		ctx.fog(false);
	}

	void scene::update(glm::vec3 const& cam)
	{
		// last position
		auto lx = last_cam.x;
		auto ly = last_cam.y;
		auto lz = last_cam.z;
		// current position
		auto cx =      cam.x;
		auto cy =      cam.y;
		auto cz =      cam.z;
		// whether to update position or not
		auto ux = (std::abs(cx-lx) > 1);
		auto uy = (std::abs(cy-ly) > 1);
		auto uz = (std::abs(cz-lz) > 1);

		// move forward
		if (uz)
			for (float iz = lz-z_range-1; iz > cz-z_range; iz--)
				for (float ix = cx-x_range; ix <= cx+x_range; ix++)
					for (float iy = cy-y_range; iy <= cy+y_range; iy++)
						generate(ix, iy, iz);

		if (uy) {
			// up
			for (float iy = ly+y_range+1; iy < cy+y_range; iy++)
				for (float ix = cx-x_range; ix <= cx+x_range; ix++)
					for (float iz = cz-z_range; iz <= cz+z_range; iz++)
						generate(ix, iy, iz);
			// down
			for (float iy = ly-y_range-1; iy > cy-y_range; iy--)
				for (float ix = cx-x_range; ix <= cx+x_range; ix++)
					for (float iz = cz-z_range; iz <= cz+z_range; iz++)
						generate(ix, iy, iz);
		}

		if (ux) {
			// right
			for (float ix = lx+x_range+1; ix < cx+x_range; ix++)
				for (float iy = cy-y_range; iy <= cy+y_range; iy++)
					for (float iz = cz-z_range; iz <= cz+z_range; iz++)
						generate(ix, iy, iz);
			// left
			for (float ix = lx-x_range-1; ix > cx-x_range; ix--)
				for (float iy = cy-y_range; iy <= cy+y_range; iy++)
					for (float iz = cz-z_range; iz <= cz+z_range; iz++)
						generate(ix, iy, iz);
		}

		// remove invisible cubes
		// because the camera is always moving forward and cannot
		// turn around, we only need to remove those whose z > cz
		cubes.remove_if([cx, cy, cz](cube const& c) {
				return (c.pos.z > cz ||
						!in_range(c.pos.y-cy, -y_range, +y_range) ||
						!in_range(c.pos.x-cx, -x_range, +x_range));
				});

		// update last_cam's each component when the change
		// is larger than one
		if (ux) {
			last_cam.x = cx;
			library::log() << "scene::update x=" << cx << "\n";
		}
		if (uy) {
			last_cam.y = cy;
			library::log() << "scene::update y=" << cy << "\n";
		}
		if (uz) {
			last_cam.z = cz;
			library::log() << "scene::update z=" << cz << "\n";
		}
	}

	bool scene::collide(glm::vec3 const& cam) const
	{
		auto it = std::find_if(
				cubes.begin(),
				cubes.end(),
				[&cam](cube const& c){ return c.contain(cam); });
		return (it != cubes.end());
	}


	/////////////////////////////////////////////////
	//
	// scene (private)
	//

	void scene::generate(float x, float y, float z)
	{
		constexpr float ns = 0.1;
		constexpr float rf = 0.5;
		constexpr float rt = 10;
		auto p = stb::perlin(x*ns, y*ns, z*ns);
		//auto p = glm::perlin(glm::vec3{x*ns, y*ns, z*ns});
		auto a = unlirp(p, rf, rt)*0.6+0.4;

		// below FIXME below
		float color[3];
		color_irp(color, a);
		// above FIXME above

		if (in_range(p, rf, rt))
			cubes.push_front({{x, y, z}, {color[0], color[1], color[2]}});

		// FIXME: sky/ground area
		//else if (ny == y-y_range) {
		//	if (a < 0) a = -a;
		//	if (a > 1) a -= (int)a;
		//	cube_add(nx, ny-16, nz, 0.6*a, a, 0.2*a);
		//}
		//else if (ny == y+y_range) {
		//	if (a < 0) a = -a;
		//	if (a > 1) a -= (int)a;
		//	cube_add(nx, ny+16, nz, 0.6*a, a, 0.2*a);
		//}
	}
}

